#ifndef AVALON_SERVICE_HPP
#define AVALON_SERVICE_HPP
#include <boost/call_traits.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/tuple/tuple.hpp>
#include "Avalon/Base/Template.hpp"
#include "Avalon/Serialization/ShuttleTuple.hpp"
#include "Avalon/Services/Message.hpp"
#include "Avalon/Services/ServiceProtocol.hpp"
#include "Avalon/Threading/AsyncHolder.hpp"

namespace Avalon {
namespace Services {

  /*! \class ServiceMessage
   *  \brief Base class for a Request or Response Message.
   */
  class ServiceMessage : public Message {
    public:
      virtual ~ServiceMessage();

      //! Returns this Message's request id.
      virtual int GetRequestId() = 0;

      //! Returns <code>true</code> iff this is a Response Message.
      virtual bool IsResponseMessage() = 0;

      //! Sets an Async to the result of this Request/Response.
      /*!
        \param async The Async to receive the result of this Request/Response.
      */
      virtual void SetAsync(Threading::BaseAsyncHolder* async);
  };

  template<typename T, typename R, typename A0 = NullType,
    typename A1 = NullType, typename A2 = NullType, typename A3 = NullType,
    typename A4 = NullType, typename A5 = NullType, typename A6 = NullType,
    typename A7 = NullType, typename A8 = NullType, typename A9 = NullType,
    typename A10 = NullType>
  class Service {};

  #define PARAMETER_TYPE(z, n, q)                                              \
    BOOST_PP_COMMA_IF(n) typename boost::call_traits<A##n>::param_type

  #define DECLARE_PARAMETER(z, n, q)                                           \
    BOOST_PP_COMMA_IF(n) typename boost::call_traits<A##n>::param_type a##n

  #define GET_TUPLE_ELEMENT(z, n, q)                                           \
    BOOST_PP_COMMA_IF(n) boost::get<n>(m_parameters)

  #define LIST_PARAMETERS(z, n, q)                                             \
    BOOST_PP_COMMA_IF(n) a ## n

  #define PARAMETER_STRUCT(z, n, q)                                            \
    struct Parameter##n {                                                      \
      typedef A##n Type;                                                       \
    };

  #define BOOST_PP_LOCAL_MACRO(n)                                              \
  template<typename T, typename R BOOST_PP_COMMA_IF(n)                         \
    BOOST_PP_ENUM_PARAMS(n, typename A)>                                       \
  class Service<T, R BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, A)> {        \
    public:                                                                    \
      typedef T Type;                                                          \
      typedef R ReturnType;                                                    \
      typedef boost::tuple<BOOST_PP_ENUM_PARAMS(n, A)> Parameters;             \
                                                                               \
      BOOST_PP_REPEAT(n, PARAMETER_STRUCT,)                                    \
                                                                               \
      static int InitializeService(const char* requestName,                    \
          const char* responseName) {                                          \
        Serialization::DataShuttle::RegisterType<Service::Request>(            \
          requestName, 0);                                                     \
        Serialization::DataShuttle::RegisterType<Service::Response>(           \
          responseName, 0);                                                    \
        return 0;                                                              \
      }                                                                        \
                                                                               \
      class Request : public ServiceMessage {                                  \
        public:                                                                \
          typedef boost::function<void (const RequestToken<Service>&           \
            BOOST_PP_COMMA_IF(n) BOOST_PP_REPEAT(n, PARAMETER_TYPE,))> Slot;   \
                                                                               \
          Request() {}                                                         \
                                                                               \
          Request(int requestId BOOST_PP_COMMA_IF(n)                           \
              BOOST_PP_REPEAT(n, DECLARE_PARAMETER,))                          \
              : m_requestId(requestId),                                        \
                m_parameters(BOOST_PP_REPEAT(n, LIST_PARAMETERS,)) {}          \
                                                                               \
          virtual ~Request() {}                                                \
                                                                               \
          virtual int GetRequestId() {                                         \
            return m_requestId;                                                \
          }                                                                    \
                                                                               \
          virtual bool IsResponseMessage() {                                   \
            return false;                                                      \
          }                                                                    \
                                                                               \
        protected:                                                             \
          virtual void EmitSignal(boost::function_base* slot,                  \
              ServiceProtocol* protocol) {                                     \
            (*static_cast<Slot*>(slot))(RequestToken<Service>(protocol,        \
              m_requestId) BOOST_PP_COMMA_IF(n)                                \
              BOOST_PP_REPEAT(n, GET_TUPLE_ELEMENT,));                         \
          }                                                                    \
                                                                               \
          virtual void Shuttle(Serialization::DataShuttle* shuttle,            \
              unsigned int version) {                                          \
            shuttle->Shuttle("request_id", m_requestId);                       \
            BOOST_PP_IF(n, shuttle->Shuttle("parameters", m_parameters);,)     \
          }                                                                    \
                                                                               \
        private:                                                               \
          friend class Serialization::DataShuttle;                             \
          int m_requestId;                                                     \
          Parameters m_parameters;                                             \
      };                                                                       \
                                                                               \
      class Response : public ServiceMessage {                                 \
        public:                                                                \
          Response() {}                                                        \
                                                                               \
          Response(int requestId, const ReturnType& result)                    \
              : m_requestId(requestId),                                        \
                m_result(result) {}                                            \
                                                                               \
          virtual ~Response() {}                                               \
                                                                               \
          virtual int GetRequestId() {                                         \
            return m_requestId;                                                \
          }                                                                    \
                                                                               \
          virtual bool IsResponseMessage() {                                   \
            return true;                                                       \
          }                                                                    \
                                                                               \
          virtual void SetAsync(Threading::BaseAsyncHolder* async) {           \
            static_cast<Threading::AsyncHolder<ReturnType>*>(                  \
              async)->GetAsync().SetResult(m_result);                          \
          }                                                                    \
                                                                               \
          const ReturnType& Get() {                                            \
            return m_result;                                                   \
          }                                                                    \
                                                                               \
        protected:                                                             \
          virtual void EmitSignal(boost::function_base* slot,                  \
              ServiceProtocol* protocol) {                                     \
            assert(false);                                                     \
          }                                                                    \
                                                                               \
          virtual void Shuttle(Serialization::DataShuttle* shuttle,            \
              unsigned int version) {                                          \
            shuttle->Shuttle("request_id", m_requestId);                       \
            shuttle->Shuttle("result", m_result);                              \
          }                                                                    \
                                                                               \
        private:                                                               \
          friend class Serialization::DataShuttle;                             \
          int m_requestId;                                                     \
          ReturnType m_result;                                                 \
      };                                                                       \
  };

  #define BOOST_PP_LOCAL_LIMITS (0, AVALON_SERVICE_PARAMETERS)
  #include BOOST_PP_LOCAL_ITERATE()
  #undef PARAMETER_TYPE
  #undef DECLARE_PARAMETER
  #undef GET_TUPLE_ELEMENT
  #undef LIST_PARAMETERS
  #undef PARAMETER_STRUCT

  #define AVALON_DECLARE_SERVICE(Type, Name, ReturnType, ...)                  \
    struct Type##Token {};                                                     \
    typedef ::Avalon::Services::Service<Type##Token, ReturnType, __VA_ARGS__>  \
      Type;                                                                    \
    namespace {                                                                \
      int Type##AVALON_UNIQUE_NAME = Type::InitializeService(Name ".Request",  \
        Name ".Response");                                                     \
    }
}
}

#endif // AVALON_SERVICE_HPP
